#include <string.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>

#include <psp2/io/devctl.h>
#include <psp2/io/dirent.h>
#include <psp2/io/fcntl.h>
#include <psp2/io/stat.h>
#include <psp2/appmgr.h>

#include <png.h>

#include "file.h"
#include "init.h"
#include "config.h"
#include "browser.h"
#include "utils.h"
#include "strnatcmp.h"
#include "lang.h"

static char *devices[] = {
    //"gro0:",
    //"grw0:",
    "imc0:",
    //"os0:",
    //"pd0:",
    //"sa0:",
    //"sd0:",
    //"tm0:",
    //"ud0:",
    "uma0:",
    "ur0:",
    "ux0:",
    //"vd0:",
    //"vs0:",
    "xmc0:",
    "host0:",
};
#define N_DEVICES (sizeof(devices) / sizeof(char **))

static char *valid_exts[] = {
    "mp4",
};
#define N_VALID_EXTS (sizeof(valid_exts) / sizeof(char **))

int isValidFile(char *path)
{
    char *ext = strrchr(path, '.');
    if (!ext++)
        return 0;

    int i = 0;
    for (i = 0; i < N_VALID_EXTS; i++)
    {
        if (strcasecmp(ext, valid_exts[i]) == 0)
            return 1;
    }

    return 0;
}

int ReadFile(const char *file, void *buf, int size)
{
    SceUID fd = sceIoOpen(file, SCE_O_RDONLY, 0);
    if (fd < 0)
        return fd;

    int read = sceIoRead(fd, buf, size);

    sceIoClose(fd);
    return read;
}

int WriteFile(const char *file, const void *buf, int size)
{
    SceUID fd = sceIoOpen(file, SCE_O_WRONLY | SCE_O_CREAT | SCE_O_TRUNC, 0777);
    if (fd < 0)
        return fd;

    int written = sceIoWrite(fd, buf, size);

    sceIoClose(fd);
    return written;
}

int allocateReadFile(const char *file, void **buffer)
{
    SceUID fd = sceIoOpen(file, SCE_O_RDONLY, 0);
    if (fd < 0)
        return fd;

    int size = sceIoLseek32(fd, 0, SCE_SEEK_END);
    sceIoLseek32(fd, 0, SCE_SEEK_SET);

    *buffer = malloc(size);
    if (!*buffer)
    {
        sceIoClose(fd);
        return -1;
    }

    int read = sceIoRead(fd, *buffer, size);
    sceIoClose(fd);

    return read;
}

int getFileSize(const char *file)
{
    SceUID fd = sceIoOpen(file, SCE_O_RDONLY, 0);
    if (fd < 0)
        return fd;

    int fileSize = sceIoLseek(fd, 0, SCE_SEEK_END);

    sceIoClose(fd);
    return fileSize;
}

int checkFileExist(const char *file)
{
    SceUID fd = sceIoOpen(file, SCE_O_RDONLY, 0);
    if (fd < 0)
        return 0;

    sceIoClose(fd);
    return 1;
}

int checkFolderExist(const char *folder)
{
    SceUID dfd = sceIoDopen(folder);
    if (dfd < 0)
        return 0;

    sceIoDclose(dfd);
    return 1;
}

int createFolder(const char *path)
{
    if (checkFolderExist(path))
        return 0;

    int len = strlen(path);
    if (len <= 0)
        return -1;

    char *_path = malloc(len + 1);
    strcpy(_path, path);
    removeEndSlash(_path);

    char ch;
    int i;
    for (i = 0; i < len; i++)
    {
        ch = _path[i];
        if (ch == '/')
        {
            _path[i] = '\0';
            sceIoMkdir(_path, 0777);
            _path[i] = ch;
        }
    }
    free(_path);

    return sceIoMkdir(path, 0777);
}

int removePath(const char *path, FileProcessParam *param)
{
    SceUID dfd = sceIoDopen(path);
    if (dfd >= 0)
    {
        int res = 0;

        do
        {
            SceIoDirent dir;
            memset(&dir, 0, sizeof(SceIoDirent));

            res = sceIoDread(dfd, &dir);
            if (res > 0)
            {
                char *new_path = malloc(strlen(path) + strlen(dir.d_name) + 2);
                snprintf(new_path, MAX_PATH_LENGTH, "%s%s%s", path, hasEndSlash(path) ? "" : "/", dir.d_name);

                if (SCE_S_ISDIR(dir.d_stat.st_mode))
                {
                    int ret = removePath(new_path, param);
                    if (ret <= 0)
                    {
                        free(new_path);
                        sceIoDclose(dfd);
                        return ret;
                    }
                }
                else
                {
                    int ret = sceIoRemove(new_path);
                    if (ret < 0)
                    {
                        free(new_path);
                        sceIoDclose(dfd);
                        return ret;
                    }

                    if (param)
                    {
                        if (param->value)
                            (*param->value)++;

                        if (param->SetProgress)
                            param->SetProgress(param->value ? *param->value : 0, param->max);

                        if (param->cancelHandler && param->cancelHandler())
                        {
                            free(new_path);
                            sceIoDclose(dfd);
                            return 0;
                        }
                    }
                }

                free(new_path);
            }
        } while (res > 0);

        sceIoDclose(dfd);

        int ret = sceIoRmdir(path);
        if (ret < 0)
            return ret;

        if (param)
        {
            if (param->value)
                (*param->value)++;

            if (param->SetProgress)
                param->SetProgress(param->value ? *param->value : 0, param->max);

            if (param->cancelHandler && param->cancelHandler())
            {
                return 0;
            }
        }
    }
    else
    {
        int ret = sceIoRemove(path);
        if (ret < 0)
            return ret;

        if (param)
        {
            if (param->value)
                (*param->value)++;

            if (param->SetProgress)
                param->SetProgress(param->value ? *param->value : 0, param->max);

            if (param->cancelHandler && param->cancelHandler())
            {
                return 0;
            }
        }
    }

    return 1;
}

int copyFile(const char *src_path, const char *dst_path, FileProcessParam *param)
{
    // The source and destination paths are identical
    if (strcasecmp(src_path, dst_path) == 0)
    {
        return -1;
    }

    // The destination is a subfolder of the source folder
    int len = strlen(src_path);
    if (strncasecmp(src_path, dst_path, len) == 0 && (dst_path[len] == '/' || dst_path[len - 1] == '/'))
    {
        return -1;
    }

    SceUID fdsrc = sceIoOpen(src_path, SCE_O_RDONLY, 0);
    if (fdsrc < 0)
        return fdsrc;

    SceUID fddst = sceIoOpen(dst_path, SCE_O_WRONLY | SCE_O_CREAT | SCE_O_TRUNC, 0777);
    if (fddst < 0)
    {
        sceIoClose(fdsrc);
        return fddst;
    }

    void *buf = memalign(4096, TRANSFER_SIZE);

    while (1)
    {
        int read = sceIoRead(fdsrc, buf, TRANSFER_SIZE);

        if (read < 0)
        {
            free(buf);

            sceIoClose(fddst);
            sceIoClose(fdsrc);

            sceIoRemove(dst_path);

            return read;
        }

        if (read == 0)
            break;

        int written = sceIoWrite(fddst, buf, read);

        if (written < 0)
        {
            free(buf);

            sceIoClose(fddst);
            sceIoClose(fdsrc);

            sceIoRemove(dst_path);

            return written;
        }

        if (param)
        {
            if (param->value)
                (*param->value) += read;

            if (param->SetProgress)
                param->SetProgress(param->value ? *param->value : 0, param->max);

            if (param->cancelHandler && param->cancelHandler())
            {
                free(buf);

                sceIoClose(fddst);
                sceIoClose(fdsrc);

                sceIoRemove(dst_path);

                return 0;
            }
        }
    }

    free(buf);

    // Inherit file stat
    SceIoStat stat;
    memset(&stat, 0, sizeof(SceIoStat));
    sceIoGetstatByFd(fdsrc, &stat);
    sceIoChstatByFd(fddst, &stat, 0x3B);

    sceIoClose(fddst);
    sceIoClose(fdsrc);

    return 1;
}

int copyPath(const char *src_path, const char *dst_path, FileProcessParam *param)
{
    // The source and destination paths are identical
    if (strcasecmp(src_path, dst_path) == 0)
    {
        return -1;
    }

    // The destination is a subfolder of the source folder
    int len = strlen(src_path);
    if (strncasecmp(src_path, dst_path, len) == 0 && (dst_path[len] == '/' || dst_path[len - 1] == '/'))
    {
        return -1;
    }

    SceUID dfd = sceIoDopen(src_path);
    if (dfd >= 0)
    {
        SceIoStat stat;
        memset(&stat, 0, sizeof(SceIoStat));
        sceIoGetstatByFd(dfd, &stat);

        stat.st_mode |= SCE_S_IWUSR;

        int ret = sceIoMkdir(dst_path, stat.st_mode & 0xFFF);
        if (ret < 0 && ret != SCE_ERROR_ERRNO_EEXIST)
        {
            sceIoDclose(dfd);
            return ret;
        }

        if (ret == SCE_ERROR_ERRNO_EEXIST)
        {
            sceIoChstat(dst_path, &stat, 0x3B);
        }

        if (param)
        {
            if (param->value)
                (*param->value) += DIRECTORY_SIZE;

            if (param->SetProgress)
                param->SetProgress(param->value ? *param->value : 0, param->max);

            if (param->cancelHandler && param->cancelHandler())
            {
                sceIoDclose(dfd);
                return 0;
            }
        }

        int res = 0;

        do
        {
            SceIoDirent dir;
            memset(&dir, 0, sizeof(SceIoDirent));

            res = sceIoDread(dfd, &dir);
            if (res > 0)
            {
                char *new_src_path = malloc(strlen(src_path) + strlen(dir.d_name) + 2);
                snprintf(new_src_path, MAX_PATH_LENGTH, "%s%s%s", src_path, hasEndSlash(src_path) ? "" : "/", dir.d_name);

                char *new_dst_path = malloc(strlen(dst_path) + strlen(dir.d_name) + 2);
                snprintf(new_dst_path, MAX_PATH_LENGTH, "%s%s%s", dst_path, hasEndSlash(dst_path) ? "" : "/", dir.d_name);

                int ret = 0;

                if (SCE_S_ISDIR(dir.d_stat.st_mode))
                {
                    ret = copyPath(new_src_path, new_dst_path, param);
                }
                else
                {
                    ret = copyFile(new_src_path, new_dst_path, param);
                }

                free(new_dst_path);
                free(new_src_path);

                if (ret <= 0)
                {
                    sceIoDclose(dfd);
                    return ret;
                }
            }
        } while (res > 0);

        sceIoDclose(dfd);
    }
    else
    {
        return copyFile(src_path, dst_path, param);
    }

    return 1;
}

int movePath(const char *src_path, const char *dst_path, int flags, FileProcessParam *param)
{
    // The source and destination paths are identical
    if (strcasecmp(src_path, dst_path) == 0)
    {
        return -1;
    }

    // The destination is a subfolder of the source folder
    int len = strlen(src_path);
    if (strncasecmp(src_path, dst_path, len) == 0 && (dst_path[len] == '/' || dst_path[len - 1] == '/'))
    {
        return -1;
    }

    int res = sceIoRename(src_path, dst_path);

    if (res == SCE_ERROR_ERRNO_EEXIST && flags & (MOVE_INTEGRATE | MOVE_REPLACE))
    {
        // Src stat
        SceIoStat src_stat;
        memset(&src_stat, 0, sizeof(SceIoStat));
        res = sceIoGetstat(src_path, &src_stat);
        if (res < 0)
            return res;

        // Dst stat
        SceIoStat dst_stat;
        memset(&dst_stat, 0, sizeof(SceIoStat));
        res = sceIoGetstat(dst_path, &dst_stat);
        if (res < 0)
            return res;

        // Is dir
        int src_is_dir = SCE_S_ISDIR(src_stat.st_mode);
        int dst_is_dir = SCE_S_ISDIR(dst_stat.st_mode);

        // One of them is a file and the other a directory, no replacement or integration possible
        if (src_is_dir != dst_is_dir)
            return -1;

        // Replace file
        if (!src_is_dir && !dst_is_dir && flags & MOVE_REPLACE)
        {
            sceIoRemove(dst_path);

            res = sceIoRename(src_path, dst_path);
            if (res < 0)
                return res;

            return 1;
        }

        // Integrate directory
        if (src_is_dir && dst_is_dir && flags & MOVE_INTEGRATE)
        {
            SceUID dfd = sceIoDopen(src_path);
            if (dfd < 0)
                return dfd;

            int res = 0;

            do
            {
                SceIoDirent dir;
                memset(&dir, 0, sizeof(SceIoDirent));

                res = sceIoDread(dfd, &dir);
                if (res > 0)
                {
                    char *new_src_path = malloc(strlen(src_path) + strlen(dir.d_name) + 2);
                    snprintf(new_src_path, MAX_PATH_LENGTH, "%s%s%s", src_path, hasEndSlash(src_path) ? "" : "/", dir.d_name);

                    char *new_dst_path = malloc(strlen(dst_path) + strlen(dir.d_name) + 2);
                    snprintf(new_dst_path, MAX_PATH_LENGTH, "%s%s%s", dst_path, hasEndSlash(dst_path) ? "" : "/", dir.d_name);

                    // Recursive move
                    int ret = movePath(new_src_path, new_dst_path, flags, param);

                    free(new_dst_path);
                    free(new_src_path);

                    if (ret <= 0)
                    {
                        sceIoDclose(dfd);
                        return ret;
                    }
                }
            } while (res > 0);

            sceIoDclose(dfd);

            // Integrated, now remove this directory
            sceIoRmdir(src_path);
        }
    }

    return 1;
}

int getNumberOfDevices()
{
    return N_DEVICES;
}

char **getDevices()
{
    return devices;
}

FileListEntry *fileListCopyEntry(FileListEntry *src)
{
    FileListEntry *dst = malloc(sizeof(FileListEntry));
    if (!dst)
        return NULL;

    memcpy(dst, src, sizeof(FileListEntry));
    dst->name = malloc(src->name_length + 1);
    strcpy(dst->name, src->name);
    return dst;
}

FileListEntry *fileListGetEntryByName(FileList *list, const char *name)
{
    if (!list)
        return NULL;

    FileListEntry *entry = list->head;

    int name_length = strlen(name);

    while (entry)
    {
        if (entry->name_length == name_length && strcasecmp(entry->name, name) == 0)
            return entry;

        entry = entry->next;
    }

    return NULL;
}

FileListEntry *fileListGetEntryByNumber(FileList *list, int n)
{
    if (!list)
        return NULL;

    FileListEntry *entry = list->head;

    while (n > 0 && entry)
    {
        n--;
        entry = entry->next;
    }

    if (n != 0)
        return NULL;

    return entry;
}

int fileListGetNumberByName(FileList *list, const char *name)
{
    if (!list)
        return -1;

    FileListEntry *entry = list->head;

    int name_length = strlen(name);

    int n = 0;

    while (entry)
    {
        if (entry->name_length == name_length && strcasecmp(entry->name, name) == 0)
            return n;

        n++;
        entry = entry->next;
    }

    return -1;
}

void fileListAddEntry(FileList *list, FileListEntry *entry, int sort)
{
    if (!list || !entry)
        return;

    entry->next = NULL;
    entry->previous = NULL;

    if (list->head == NULL)
    {
        list->head = entry;
        list->tail = entry;
    }
    else
    {
        if (sort != SORT_NONE)
        {
            FileListEntry *p = list->head;
            FileListEntry *previous = NULL;

            char entry_name[MAX_NAME_LENGTH];
            strcpy(entry_name, entry->name);
            removeEndSlash(entry_name);

            while (p)
            {
                char p_name[MAX_NAME_LENGTH];
                strcpy(p_name, p->name);
                removeEndSlash(p_name);

                // Sort by type
                if (sort == SORT_BY_NAME)
                {
                    // First folders then files
                    if (entry->is_folder > p->is_folder)
                        break;

                    // Sort by name within the same type
                    if (entry->is_folder == p->is_folder)
                    {
                        if (strnatcasecmp(entry_name, p_name) < 0)
                        {
                            break;
                        }
                    }
                }

                previous = p;
                p = p->next;
            }

            if (previous == NULL)
            { // Order: entry (new head) -> p (old head)
                entry->next = p;
                p->previous = entry;
                list->head = entry;
            }
            else if (previous->next == NULL)
            { // Order: p (old tail) -> entry (new tail)
                FileListEntry *tail = list->tail;
                tail->next = entry;
                entry->previous = tail;
                list->tail = entry;
            }
            else
            { // Order: previous -> entry -> p
                previous->next = entry;
                entry->previous = previous;
                entry->next = p;
                p->previous = entry;
            }
        }
        else
        {
            FileListEntry *tail = list->tail;
            tail->next = entry;
            entry->previous = tail;
            list->tail = entry;
        }
    }

    list->length++;
}

int fileListRemoveEntry(FileList *list, FileListEntry *entry)
{
    if (!list || !entry)
        return 0;

    if (entry->previous)
    {
        entry->previous->next = entry->next;
    }
    else
    {
        list->head = entry->next;
    }

    if (entry->next)
    {
        entry->next->previous = entry->previous;
    }
    else
    {
        list->tail = entry->previous;
    }

    list->length--;
    free(entry->name);
    free(entry);

    if (list->length == 0)
    {
        list->head = NULL;
        list->tail = NULL;
    }

    return 1;
}

int fileListRemoveEntryByName(FileList *list, const char *name)
{
    if (!list)
        return 0;

    FileListEntry *entry = list->head;
    FileListEntry *previous = NULL;

    int name_length = strlen(name);

    while (entry)
    {
        if (entry->name_length == name_length && strcasecmp(entry->name, name) == 0)
        {
            if (previous)
            {
                previous->next = entry->next;
            }
            else
            {
                list->head = entry->next;
            }

            if (list->tail == entry)
            {
                list->tail = previous;
            }

            list->length--;
            free(entry->name);
            free(entry);

            if (list->length == 0)
            {
                list->head = NULL;
                list->tail = NULL;
            }

            return 1;
        }

        previous = entry;
        entry = entry->next;
    }

    return 0;
}

void fileListSelectAll(FileList *list)
{
    if (!list)
        return;

    list->selected_num = 0;

    FileListEntry *entry = list->head;

    while (entry)
    {
        entry->is_selected = 1;
        list->selected_num++;
        entry = entry->next;
    }
}

void fileListUnselectAll(FileList *list)
{
    if (!list)
        return;

    list->selected_num = list->length;

    FileListEntry *entry = list->head;

    while (entry)
    {
        entry->is_selected = 0;
        list->selected_num--;
        entry = entry->next;
    }
}

void fileListEmpty(FileList *list)
{
    if (!list)
        return;

    FileListEntry *entry = list->head;

    while (entry)
    {
        FileListEntry *next = entry->next;
        free(entry->name);
        free(entry);
        entry = next;
    }

    list->head = NULL;
    list->tail = NULL;
    list->length = 0;
    list->files = 0;
    list->folders = 0;
}

int fileListGetDeviceEntries(FileList *list)
{
    if (!list)
        return -1;

    int i;
    for (i = 0; i < N_DEVICES; i++)
    {
        if (devices[i])
        {
            if (is_safe_mode && strcmp(devices[i], "ux0:") != 0)
                continue;

            SceIoStat stat;
            memset(&stat, 0, sizeof(SceIoStat));
            if (sceIoGetstat(devices[i], &stat) >= 0)
            {
                FileListEntry *entry = malloc(sizeof(FileListEntry));
                if (entry)
                {
                    entry->is_selected = 0;
                    entry->name_length = strlen(devices[i]);
                    entry->name = malloc(entry->name_length + 1);
                    strcpy(entry->name, devices[i]);
                    entry->is_folder = 1;

                    memcpy(&entry->ctime, (SceDateTime *)&stat.st_ctime, sizeof(SceDateTime));
                    memcpy(&entry->mtime, (SceDateTime *)&stat.st_mtime, sizeof(SceDateTime));
                    memcpy(&entry->atime, (SceDateTime *)&stat.st_atime, sizeof(SceDateTime));

                    char date_string[16], time_string[24];
                    getDateString(date_string, date_format, &entry->mtime);
                    getTimeString(time_string, time_format, &entry->mtime);
                    char size_string[36];

                    SceIoDevInfo info;
                    memset(&info, 0, sizeof(SceIoDevInfo));
                    int res = sceIoDevctl(entry->name, 0x3001, NULL, 0, &info, sizeof(SceIoDevInfo));
                    if (res >= 0)
                    {
                        entry->size = info.free_size;
                        entry->size2 = info.max_size;
                    }
                    else
                    {
                        if (strcmp(devices[i], "ux0:") == 0)
                        {
                            sceAppMgrGetDevInfo("ux0:", (uint64_t *)&entry->size2, (uint64_t *)&entry->size);
                        }
                        else
                        {
                            entry->size = 0;
                            entry->size2 = 0;
                        }
                    }

                    char used_size_string[16], max_size_string[16];
                    if (entry->size != 0 && entry->size2 != 0)
                    {
                        getSizeString(used_size_string, entry->size2 - entry->size);
                        getSizeString(max_size_string, entry->size2);
                    }
                    else
                    {
                        strcpy(used_size_string, "-");
                        strcpy(max_size_string, "-");
                    }
                    snprintf(size_string, 36, "%s / %s", used_size_string, max_size_string);
                    int info_size = strlen(date_string) + 1 + strlen(time_string) + 2 + strlen(size_string) + 1;
                    entry->info = malloc(info_size);
                    snprintf(entry->info, info_size, "%s %s  %s", date_string, time_string, size_string);

                    fileListAddEntry(list, entry, SORT_BY_NAME);

                    list->folders++;
                }
            }
        }
    }

    list->selected_num = 0;

    return 0;
}

int fileListGetDirectoryEntries(FileList *list, const char *path, int sort)
{
    if (!list)
        return -1;

    SceUID dfd = sceIoDopen(path);
    if (dfd < 0)
        return dfd;

    int res = 0;

    do
    {
        SceIoDirent dir;
        memset(&dir, 0, sizeof(SceIoDirent));

        res = sceIoDread(dfd, &dir);
        if (res > 0)
        {
            if (!SCE_S_ISDIR(dir.d_stat.st_mode) && !isValidFile(dir.d_name))
                continue;

            FileListEntry *entry = malloc(sizeof(FileListEntry));
            if (entry)
            {
                entry->is_selected = 0;
                entry->is_folder = SCE_S_ISDIR(dir.d_stat.st_mode);

                entry->size = dir.d_stat.st_size;
                memcpy(&entry->ctime, (SceDateTime *)&dir.d_stat.st_ctime, sizeof(SceDateTime));
                memcpy(&entry->mtime, (SceDateTime *)&dir.d_stat.st_mtime, sizeof(SceDateTime));
                memcpy(&entry->atime, (SceDateTime *)&dir.d_stat.st_atime, sizeof(SceDateTime));

                char date_string[16], time_string[24];
                getDateString(date_string, date_format, &entry->mtime);
                getTimeString(time_string, time_format, &entry->mtime);
                char size_string[16];

                if (entry->is_folder)
                {
                    entry->name_length = strlen(dir.d_name) + 1;
                    entry->name = malloc(entry->name_length + 1);
                    strcpy(entry->name, dir.d_name);
                    addEndSlash(entry->name);
                    list->folders++;

                    strcpy(size_string, STR_FOLDER);
                }
                else
                {
                    entry->name_length = strlen(dir.d_name);
                    entry->name = malloc(entry->name_length + 1);
                    strcpy(entry->name, dir.d_name);
                    list->files++;

                    getSizeString(size_string, entry->size);
                }
                int info_size = strlen(date_string) + 1 + strlen(time_string) + 2 + strlen(size_string) + 1;
                entry->info = malloc(info_size);
                snprintf(entry->info, info_size, "%s %s  %s", date_string, time_string, size_string);

                fileListAddEntry(list, entry, sort);
            }
        }
    } while (res > 0);

    sceIoDclose(dfd);

    list->selected_num = 0;

    return 0;
}

int fileListGetEntries(FileList *list, const char *path, int sort)
{
    if (!list)
        return -1;

    if (strcasecmp(path, HOME_PATH) == 0)
    {
        return fileListGetDeviceEntries(list);
    }

    return fileListGetDirectoryEntries(list, path, sort);
}
// get directory path from filename
// result has slash at the end
char *getBaseDirectory(const char *path)
{
    int i;
    int sep_ind = -1;
    int len = strlen(path);
    if (len > MAX_PATH_LENGTH - 1 || len <= 0)
        return NULL;
    for (i = len - 1; i >= 0; i--)
    {
        if (path[i] == '/' || path[i] == ':')
        {
            sep_ind = i;
            break;
        }
    }
    if (sep_ind == -1)
        return NULL;

    char *res = (char *)malloc(MAX_PATH_LENGTH);
    if (!res)
        return NULL;

    strncpy(res, path, MAX_PATH_LENGTH);
    res[sep_ind + 1] = '\0';
    return res;
}

// returns NULL when no filename found or error
// result is at most MAX_PATH_LEN
char *getFilename(const char *path)
{
    int i;
    int sep_ind = -1;
    int len = strlen(path);
    if (len > MAX_PATH_LENGTH || len <= 0)
        return NULL;

    if (path[len - 1] == '/' || path[len - 1] == ':')
        return NULL; // no file

    for (i = len - 1; i >= 0; i--)
    {
        if (path[i] == '/' || path[i] == ':')
        {
            sep_ind = i;
            break;
        }
    }
    if (sep_ind == -1)
        return NULL;
    char *res = (char *)malloc(MAX_PATH_LENGTH);
    if (!res)
        return NULL;

    int new_len = len - (sep_ind + 1);
    strncpy(res, path + (sep_ind + 1), new_len); // dont copy separation char
    if (new_len + 1 < MAX_PATH_LENGTH)
        res[new_len] = '\0';
    else
        res[MAX_PATH_LENGTH - 1] = '\0';
    return res;
}

char *getBaseName(const char *path)
{
    int i;
    int len = strlen(path);
    if (len <= 0)
        return NULL;

    int sep_ind = -1;
    int sep_len = len;

    for (i = len - 1; i >= 0; i--)
    {
        if (path[i] == '/' || path[i] == ':')
        {
            sep_ind = i;
            break;
        }
        else if (path[i] == '.' && sep_len == len)
        {
            sep_len = i;
        }
    }

    int new_len = sep_len - (sep_ind + 1);
    char *res = (char *)malloc(new_len + 1);
    if (!res)
        return NULL;
    if (new_len > 0)
        strncpy(res, path + (sep_ind + 1), new_len);
    res[new_len] = '\0';

    return res;
}

int makeBaseDirectory(char *dst_path, const char *src_path, int size)
{
    int i;
    int sep_ind = -1;
    int len = strlen(src_path);

    if (len <= 0)
        return -1;

    for (i = len - 1; i >= 0; i--)
    {
        if (src_path[i] == '/' || src_path[i] == ':')
        {
            sep_ind = i;
            break;
        }
    }
    if (sep_ind == -1)
        return -1;

    int new_len = sep_ind + 1;
    if (new_len > size - 1)
        new_len = size - 1;
    if (new_len > 0)
        strncpy(dst_path, src_path, new_len);
    dst_path[new_len] = '\0';

    return 0;
}

int makeFilename(char *dst_name, const char *src_path, int size)
{
    int i;
    int sep_ind = -1;
    int len = strlen(src_path);

    if (len <= 0)
        return -1;

    if (src_path[len - 1] == '/' || src_path[len - 1] == ':')
        return -1;

    for (i = len - 1; i >= 0; i--)
    {
        if (src_path[i] == '/' || src_path[i] == ':')
        {
            sep_ind = i;
            break;
        }
    }
    if (sep_ind == -1)
        return -1;

    int new_len = len - (sep_ind + 1);
    if (new_len > size - 1)
        new_len = size - 1;
    if (new_len > 0)
        strncpy(dst_name, src_path + (sep_ind + 1), new_len);
    dst_name[new_len] = '\0';

    return 0;
}

int makeBaseName(char *dst_name, const char *src_path, int size)
{
    int i;
    int len = strlen(src_path);
    int sep_ind = -1;
    int sep_len = len;

    if (len <= 0)
        return -1;

    for (i = len - 1; i >= 0; i--)
    {
        if (src_path[i] == '/' || src_path[i] == ':')
        {
            sep_ind = i;
            break;
        }
        else if (src_path[i] == '.' && sep_len == len)
        {
            sep_len = i;
        }
    }

    int new_len = sep_len - (sep_ind + 1);
    if (new_len > size - 1)
        new_len = size - 1;
    if (new_len > 0)
        strncpy(dst_name, src_path + (sep_ind + 1), new_len);
    dst_name[new_len] = '\0';

    return 0;
}

int writePngFile(const char *path, unsigned char *pixels, int width, int height, int bit_depth)
{
    FILE *png_file = fopen(path, "wb");
    if (!png_file)
    {
        return -1;
    }

    png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
    if (png_ptr == NULL)
    {
        fclose(png_file);
        return -1;
    }

    png_infop info_ptr = png_create_info_struct(png_ptr);
    if (info_ptr == NULL)
    {
        png_destroy_write_struct(&png_ptr, NULL);
        return -1;
    }

    png_init_io(png_ptr, png_file);
    png_set_IHDR(png_ptr, info_ptr, width, height, bit_depth, PNG_COLOR_TYPE_RGB_ALPHA,
                 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);

    png_write_info(png_ptr, info_ptr);
    png_set_packing(png_ptr);

    png_bytepp row_ptr = (png_bytepp)png_malloc(png_ptr, height * sizeof(png_bytep));
    for (int i = 0; i < height; i++)
    {
        row_ptr[i] = (png_bytep)(pixels + i * width * 4);
    }

    png_write_image(png_ptr, row_ptr);
    png_write_end(png_ptr, info_ptr);
    png_free(png_ptr, row_ptr);
    png_destroy_write_struct(&png_ptr, &info_ptr);
    fclose(png_file);

    return 0;
}
